import io.activej.codegen.ClassGenerator;
import io.activej.codegen.DefiningClassLoader;
import io.activej.codegen.expression.impl.HashCode;
import io.activej.codegen.expression.impl.ToString;

import static io.activej.codegen.expression.Expressions.*;

/**
 * In this example a Class that implements the specified interface is dynamically created.
 * Methods are constructed programmatically using our fluent API built on top of ASM.
 */
public class DynamicClassCreationExample {
	public static final DefiningClassLoader CLASS_LOADER = DefiningClassLoader.create();

	public static void main(String[] args) throws ReflectiveOperationException {
		//[START REGION_2]
		// declare fields
		// setter for both fields - a sequence of actions
		// compareTo, equals, hashCode and toString methods implementations follow the standard convention
		Class<Person> personClass = ClassGenerator.builder(Person.class)
			// declare fields
			.withField("id", int.class)
			.withField("name", String.class)

			// setter for both fields - a sequence of actions
			.withMethod("setIdAndName", sequence(
				set(property(self(), "id"), arg(0)),
				set(property(self(), "name"), arg(1))))
			.withMethod("getId", property(self(), "id"))
			.withMethod("getName", property(self(), "name"))

			// compareTo, equals, hashCode and toString methods implementations follow the standard convention
			.withMethod("int compareTo(Person)", comparableImpl("id", "name"))
			.withMethod("equals", equalsImpl("id", "name"))
			.withMethod("hash", hashCodeImpl("id", "name"))
			.withMethod("hashOfPojo", HashCode.builder()
				.with(property(arg(0), "id"))
				.with(property(arg(0), "name"))
				.build())
			.withMethod("toString", ToString.builder()
				.withField("id")
				.with("name", property(self(), "name"))
				.build())
			.build()
			.generateClass(CLASS_LOADER);
		//[END REGION_2]

		//[START REGION_3]
		// Instantiate two objects of dynamically defined class
		Person jack = personClass.getDeclaredConstructor().newInstance();
		Person martha = personClass.getDeclaredConstructor().newInstance();

		jack.setIdAndName(5, "Jack");
		martha.setIdAndName(jack.getId() * 2, "Martha");

		System.out.println("First person: " + jack);
		System.out.println("Second person: " + martha);

		System.out.println("jack.equals(martha) ? : " + jack.equals(martha));
		//[END REGION_3]

		// Compare dynamically created hashing implementation with the conventional one
		ExamplePojo examplePojo = new ExamplePojo(5, "Jack");
		System.out.println(examplePojo);
		System.out.println("jack.hash(examplePojo)  = " + jack.hashOfPojo(examplePojo));
		System.out.println("jack.hash()             = " + jack.hash());
		System.out.println("examplePojo.hashCode()  = " + examplePojo.hashCode());
	}

	//[START REGION_1]
	@SuppressWarnings({"unused", "NullableProblems"})
	public interface Person extends Comparable<Person> {
		void setIdAndName(int id, String name);

		int getId();

		String getName();

		int hashOfPojo(ExamplePojo personPojo);

		int hash();

		@Override
		int compareTo(Person o);

		@Override
		String toString();

		@Override
		boolean equals(Object obj);
	}
	//[END REGION_1]

	@SuppressWarnings("unused")
	public static class ExamplePojo {
		final int id;
		final String name;

		ExamplePojo(int id, String name) {
			this.id = id;
			this.name = name;
		}

		public int getId() {
			return id;
		}

		public String getName() {
			return name;
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) return true;
			if (o == null || getClass() != o.getClass()) return false;
			ExamplePojo that = (ExamplePojo) o;
			return id == that.id && name.equals(that.name);
		}

		@Override
		public int hashCode() {
			int result = id;
			result = 31 * result + name.hashCode();
			return result;
		}

		@Override
		public String toString() {
			return "ExamplePojo{id=" + id + ", name=" + name + '}';
		}
	}
}
